package dulRadio;

import java.util.LinkedList;

public class Board{
	private int id;
	private String state;
	private LinkedList<Integer[]> adcData;
	private LinkedList<Integer[]> accData;
	private int smoothFactor;

	// Minimum movement force to consider.
	private int MIN_FORCE = 250;

	// Minimum times in a shake gesture that the direction of movement needs to
	// change.
	private int MIN_DIRECTION_CHANGE = 4;

	// Maximum pause between movements.
	private int MAX_PAUSE_BETHWEEN_DIRECTION_CHANGE = 200;

	// Maximum allowed time for shake gesture.
	private int MAX_TOTAL_DURATION_OF_SHAKE = 1000;

	// Time when the gesture started.
	private long mFirstDirectionChangeTime = 0;

	// Time when the last movement started.
	private long mLastDirectionChangeTime;

	// How many movements are considered so far.
	private int mDirectionChangeCount = 0;

	// The last x position.
	private float lastX = 0;

	// The last y position.
	private float lastY = 0;

	// The last z position.
	private float lastZ = 0;

	/**
	 * 
	 * Constructor for a single sensor board
	 * 
	 * @param id ID of the actual sensor board you want to access
	 */
	public Board(int id){
		adcData=new LinkedList<Integer[]>();
		accData=new LinkedList<Integer[]>();
		smoothFactor=10;
		this.id=id;
		state="";
	}
	
	/**
	 * 
	 * @return The id of the Sensor board
	 */
	public int getID(){
		return id;
	}
	
	/**
	 * 
	 * @return The actual state of the sensor board (active, inactive, off, sleeping etc.)
	 */
	public String getState(){
		return state;
	}
	
	public Integer[] getCurrentData(){
		Integer[] currdata= new Integer[5];
		currdata[0]=0;
		currdata[1]=0;
		currdata[2]=0;
		currdata[3]=0;
		currdata[4]=0;
		
		if(adcData.size()>0){
			currdata[0]=adcData.getLast()[0];
			currdata[1]=adcData.getLast()[1];
		}
		if(accData.size()>0){
			currdata[2]=accData.getLast()[0];
			currdata[3]=accData.getLast()[1];
			currdata[4]=accData.getLast()[2];
		}
		
		return currdata;
	}

	/**
	 * Adds data to the sensor board and makes sure that there are the correct amount of items in the lists.
	 * adc and acc lists can be of different size!
	 * 
	 * @param d
	 */
	//Only call if serial incoming is not null
	public void addData(Object[] d){
		if(d.length==4){
			Integer[] adc={(Integer)d[1],(Integer)d[2]};
			while(adcData.size()>smoothFactor)
				adcData.removeFirst();
			adcData.add(adc);
			state=(String)d[3];
		}
		else if(d.length==5){
			Integer[] acc={(Integer)d[1],(Integer)d[2],(Integer)d[3]};
			while(accData.size()>smoothFactor)
				accData.removeFirst();
			accData.add(acc);
			state=(String)d[4];
		}
	}
	
	/**
	 * Configure when a shake is registered
	 * 
	 * @param minForce The minimum force to consider
	 * @param minDirChange Minimum times in a shake gesture that the direction of movement needs to change.
	 * @param maxPausBtwnDirChg Maximum pause between movements.
	 * @param maxTotDurShk Maximum allowed time for shake gesture.
	 */
	public void configShakeEvent(int minForce, int minDirChange,
			int maxPausBtwnDirChg, int maxTotDurShk) {
		MIN_FORCE = minForce;
		MIN_DIRECTION_CHANGE = minDirChange;
		MAX_PAUSE_BETHWEEN_DIRECTION_CHANGE = maxPausBtwnDirChg;
		MAX_TOTAL_DURATION_OF_SHAKE = maxTotDurShk;
	}
	
	/**
	 * @param s number of samples to smooth over
	 */
	public void setSmoothSamples(int s){
		smoothFactor=s;
	}
	
	/**
	 * 
	 * @TODO Consider making this event based. Now you do not get nescecarily get the latest data 
	 * 
	 * @return A list of smoothed values from the sensor board with this id
	 */
	public int[] smoothValues(){
			LinkedList<Integer[]> tempadc=(LinkedList<Integer[]>)adcData.clone();
			LinkedList<Integer[]> tempacc=(LinkedList<Integer[]>)accData.clone();
			
			int[] t=new int[5];
			int adc1=0;
			int adc2=0;
			int accx=0;
			int accy=0;
			int accz=0;
			
			for(Integer[] adc : tempadc){
				adc1+=adc[0];
				adc2+=adc[1];
			}
			
			for(Integer[] acc : tempacc){
				accx+=acc[0];
				accy+=acc[1];
				accz+=acc[2];
			}
			
			t[0]=adc1/smoothFactor;
			t[1]=adc2/smoothFactor;
			t[2]=accx/smoothFactor;
			t[3]=accy/smoothFactor;
			t[4]=accz/smoothFactor;
		
		return t;
	}
	
	/**
	 * Only possible to check x and z axes.
	 * 
	 * Y axis is on experimental level!!!
	 * 
	 * @return 360 degree position for x (0) and z (2). Y (1) is 180 degree 
	 */
	public int[] getTiltPosition(){
		int[] result={0,0,0};
		if(accData.size()>0){
			Integer[] accD=accData.getLast();
			float degreeFactor=90f/255f;
			
					if(accD[1]>=0){
						
						//X axis
						if(accD[2]>=0){
							if(accD[2]==0)
								result[0]=0;
							else
								result[0]=(int) (degreeFactor*accD[2]);
						}else if(accD[2]<0)
							result[0]=(int) (359+(degreeFactor*accD[2]));
						
						//Z axis
						if(accD[0]>=0){
							if(accD[0]==0)
								result[2]=0;
							else
								result[2]=(int) (degreeFactor*accD[0]);
						}else if(accD[0]<0)
							result[2]=(int) (359+(degreeFactor*accD[0]));
						
						//Y axis
						if(accD[1]==0){
							result[1]=90;
						}else{
							result[1]=(int)(89-degreeFactor*accD[1]);
							if(result[1]<0)
								result[1]=0;
						}
					}
					else if(accD[1]<0){
						
						//X axis
						if(accD[2]>=0){
							if(accD[2]==0)
								result[0]=180;
							else
								result[0]=(int) (180-(degreeFactor*accD[2]));
						}else if(accD[2]<0)
							result[0]=(int) (181-(degreeFactor*accD[2]));
						
						//z axis
						if(accD[0]>=0){
							if(accD[0]==0)
								result[2]=180;
							else
								result[2]=(int) (180-(degreeFactor*accD[0]));
						}else if(accD[0]<0)
							result[2]=(int) (181-(degreeFactor*accD[0]));
						
						//Y axis
						result[1]=(int)((degreeFactor*accD[1])*-1)+90;
						if(result[1]>180)
							result[1]=180;
					}
				
		}
		return result;
	}
	
	/**
	 * Checks if the DUL Radio is being shaken
	 */
	public boolean isShaking() {
		boolean shaking=false;
		
		// get sensor data
		float x = accData.getLast()[0];
		float y = accData.getLast()[1];
		float z = accData.getLast()[2];

		// calculate movement
		float totalMovement = Math.abs(x + y + z - lastX - lastY - lastZ);

		if (totalMovement > MIN_FORCE) {

			// get time
			long now = System.currentTimeMillis();

			// store first movement time
			if (mFirstDirectionChangeTime == 0) {
				mFirstDirectionChangeTime = now;
				mLastDirectionChangeTime = now;
			}

			// check if the last movement was not long ago
			long lastChangeWasAgo = now - mLastDirectionChangeTime;
			if (lastChangeWasAgo < MAX_PAUSE_BETHWEEN_DIRECTION_CHANGE) {

				// store movement data
				mLastDirectionChangeTime = now;
				mDirectionChangeCount++;

				// store last sensor data
				lastX = x;
				lastY = y;
				lastZ = z;

				// check how many movements are so far
				if (mDirectionChangeCount >= MIN_DIRECTION_CHANGE) {

					// check total duration
					long totalDuration = now - mFirstDirectionChangeTime;
					if (totalDuration < MAX_TOTAL_DURATION_OF_SHAKE) {
						shaking=true;
						resetShakeParameters();
					}
				}
			} else {
				resetShakeParameters();
			}
		}
		
		return shaking;
	}
	
	/**
	 * Resets the shake parameters to their default values.
	 */
	private void resetShakeParameters() {
		mFirstDirectionChangeTime = 0;
		mDirectionChangeCount = 0;
		mLastDirectionChangeTime = 0;
		lastX = 0;
		lastY = 0;
		lastZ = 0;
	}
}